El método Steffen: por qué el embarque «perfecto» de aviones sigue sin funcionar

Cada cierto tiempo vuelve a circular el mismo vídeo. Una simulación con filas de asientos que se llenan en un orden matemático impecable, un locutor asegurando que el problema del embarque está resuelto desde 2008, y un titular que se repite en decenas de publicaciones cada año: «el método Steffen es dos veces más rápido y ninguna aerolínea lo usa por pura tozudez».

Este verano lo comprobé en directo. Vuelo interno saliendo de Yogyakarta, Indonesia. La aerolínea había montado grupos de embarque complejos: por ventanilla, por centro y por pasillo, más grupos de los habituales. Un intento claro de aplicar algo parecido a lo del vídeo.

El resultado fue caos absoluto. Pasajeros preguntando qué grupo les tocaba, gente colándose, padres preguntando si tenían que separarse de sus hijos porque estaban en grupos distintos. Cada grupo tardó más de cinco minutos en aclararse. El embarque completo fue más lento que uno cualquiera sin ninguna estrategia.

No fue sorpresa. La simulación del vídeo no tiene familias, ni pasajeros con tarjeta de prioridad, ni retrasos, ni conexiones ajustadas, ni a esa persona que llega corriendo a la puerta de embarque cuando ya casi han terminado. Tampoco tiene a una sola persona con un micrófono intentando colocar a 180 desconocidos en el orden exacto que pide un paper académico.

Quién inventó esto y qué decía realmente

El método existe y el estudio es real. Lo publicó en 2008 Jason Steffen, entonces investigador postdoctoral en astrofísica (hoy en la Universidad de Nevada, Las Vegas), en la revista Journal of Air Transport Management. Steffen venía de trabajar con métodos de Monte Carlo basados en cadenas de Markov (técnicas estadísticas para simular sistemas con muchas piezas interactuando entre sí) y aplicó esa misma lógica al problema de meter a 150 personas en un tubo con un único pasillo.

Su secuencia, en la versión más citada, es esta:

  1. Ventanilla, filas impares (por ejemplo la 1, la 3, la 5...)
  2. Ventanilla, filas pares
  3. Centro, filas impares
  4. Centro, filas pares
  5. Pasillo, filas impares
  6. Pasillo, filas pares

La idea de fondo tiene dos partes, y ahí está lo interesante desde el punto de vista de algoritmos. Por un lado, nadie tiene que pedirle a alguien ya sentado que se levante, porque quien va a ventanilla siempre embarca antes que quien va a centro o pasillo en esa misma fila. Por otro, dos pasajeros que embarcan seguidos van siempre a filas alejadas entre sí, algo que casi nadie explica cuando comparte el vídeo: eso evita que se acumulen varias personas guardando el equipaje en el mismo tramo corto de pasillo a la vez. No es solo «ventanilla antes que pasillo», es una cuestión de espaciado, muy parecida a por qué en un sistema con un único recurso compartido interesa repartir las peticiones en el tiempo y no dejar que se agrupen.

Infografía comparando los seis métodos de embarque de aviones: trasera a delantera, delantera a trasera, por zonas, ventanilla-centro-pasillo, método Steffen y embarque aleatorio

Los números del experimento, sin exagerar

En 2011, Steffen y su equipo probaron el método con personas de verdad: 72 voluntarios y extras de Hollywood, en un fuselaje de un 757 a tamaño real montado en un plató de Air Hollywood, en California, con 12 filas de seis asientos. El resultado, publicado también en Journal of Air Transport Management (artículo completo en arXiv):

Método

Tiempo de embarque

Bloques (grupos por zonas)

6 min 54 s

Trasera a delantera

6 min 11 s

Aleatorio (sin asignación)

4 min 44 s

WilMA (ventanilla → centro → pasillo)

4 min 13 s

Steffen

3 min 36 s

Los datos son reales y la diferencia es notable: casi la mitad de tiempo que el método tradicional de zonas. Lo que casi nunca se cuenta es la letra pequeña del propio estudio: fue un único pase por método, no varias repeticiones, y con voluntarios pagados que se comportaron exactamente como el protocolo pedía. Ni un niño que no quiere sentarse, ni un pasajero de tarjeta oro que exige subir primero, ni una pareja que se niega a separarse un minuto.

Por qué ninguna aerolínea lo usa después de 15 años

En 2014 el propio Steffen reconoció a Wired que, pese al entusiasmo del público, ninguna aerolínea había adoptado el método. Más de una década después, sigue siendo así.

El dato más revelador viene de una prueba real, no de la teoría: United Airlines hizo una prueba A/B comparando WilMA contra el método Steffen en vuelos comerciales, y ganó WilMA. El modelo de Steffen no está mal calculado: exige una fila perfectamente ordenada, y ni el personal de embarque ni los pasajeros están dispuestos a sostener ese orden durante los dos o tres minutos que hacen falta para llamar a cada grupo. El propio Steffen ha admitido después que el sistema de embarque libre de Southwest, sin asientos asignados, donde cada uno se sienta donde puede, funciona mejor en la práctica que su propio método optimizado.

La lista de motivos que la teoría no contempla es larga y cualquiera que haya volado la reconoce: grupos familiares que necesitan sentarse juntos, pasajeros con movilidad reducida, tarjetas de fidelización que compran el derecho a subir antes, conexiones ajustadas, equipaje de mano que ya no cabe cuando llegas a tu fila. Cada «solo hay que aplicar el orden» esconde tres restricciones que nadie mencionó en el vídeo.

Reproduciendo el problema en código

Como el artículo va de programación, vamos a comprobarlo con una simulación en Python en vez de quedarnos con la superioridad moral del método Steffen. La idea es modelar el pasillo del avión como un autómata celular: cada fila es una celda, un pasajero avanza una celda por segundo si la siguiente está libre, y al llegar a su fila se detiene el tiempo que tarda en guardar el equipaje (con algo de aleatoriedad, porque no todo el mundo guarda la maleta en cinco segundos).

No es una réplica exacta del modelo del paper (esos detalles no están publicados con el nivel de precisión necesario para reproducirlos al milímetro), pero captura los dos mecanismos que de verdad importan: la interferencia de asiento (tener que levantarte para dejar pasar a alguien) y la interferencia de pasillo (quedarte parado detrás de quien guarda su maleta delante de ti).

"""
Simulacion simplificada de metodos de embarque de aviones.
Modelo tipo automata celular: un unico pasillo, un pasajero por celda,
tiempo de guardado de equipaje aleatorio, interferencia de asiento
(si alguien tiene la ventanilla y el pasillo/centro ya esta ocupado,
tiene que esperar a que se levanten).

No pretende reproducir el paper de Steffen al milimetro: es una
version legible pensada para ilustrar por que el orden de embarque
importa y por que la teoria y la practica no siempre coinciden.
"""

import random
from dataclasses import dataclass
from statistics import mean, stdev

ROWS = 20
SIDES = ("izq", "der")
SEAT_TYPES = ("ventanilla", "centro", "pasillo")  # de fuera a dentro
SEAT_RANK = {"ventanilla": 0, "centro": 1, "pasillo": 2}


@dataclass
class Pasajero:
    fila: int
    lado: str
    tipo_asiento: str
    tiempo_guardado: float


def generar_pasajeros(filas=ROWS, seed=None):
    rng = random.Random(seed)
    pasajeros = []
    for fila in range(1, filas + 1):
        for lado in SIDES:
            for tipo in SEAT_TYPES:
                # la mayoria guarda la maleta en 8-15s, algunos tardan mucho mas
                tiempo = rng.triangular(6, 35, 12)
                pasajeros.append(Pasajero(fila, lado, tipo, tiempo))
    return pasajeros


# --- Metodos de embarque ---

def orden_aleatorio(pasajeros, rng):
    p = pasajeros[:]
    rng.shuffle(p)
    return p


def orden_trasera_delantera(pasajeros, rng, bloques=4):
    # se divide el avion en bloques de filas, el mas trasero embarca primero
    filas = sorted({p.fila for p in pasajeros})
    tam = -(-len(filas) // bloques)  # redondeo hacia arriba
    grupos = [filas[i:i + tam] for i in range(0, len(filas), tam)]
    grupos.sort(key=lambda g: -max(g))  # filas mas altas (traseras) primero
    orden = []
    for grupo in grupos:
        del_grupo = [p for p in pasajeros if p.fila in grupo]
        rng.shuffle(del_grupo)
        orden.extend(del_grupo)
    return orden


def orden_wilma(pasajeros, rng):
    # Window-Middle-Aisle: todas las ventanillas, luego centro, luego pasillo
    p = pasajeros[:]
    rng.shuffle(p)
    p.sort(key=lambda x: SEAT_RANK[x.tipo_asiento])
    return p


def _espaciar(filas):
    """Reordena una lista de filas alternando extremos (1, 19, 3, 17, 5, 15...)
    para que dos pasajeros consecutivos nunca vayan a filas cercanas: el de
    delante ya se ha sentado cuando el de detras lo alcanza."""
    filas = sorted(filas)
    i, j = 0, len(filas) - 1
    salida = []
    tomar_del_inicio = True
    while i <= j:
        if tomar_del_inicio:
            salida.append(filas[i])
            i += 1
        else:
            salida.append(filas[j])
            j -= 1
        tomar_del_inicio = not tomar_del_inicio
    return salida


def orden_steffen(pasajeros, rng):
    # ventanilla filas impares, ventanilla filas pares, centro impares,
    # centro pares, pasillo impares, pasillo pares. Dentro de cada grupo,
    # las filas se recorren de fuera hacia dentro (ver _espaciar) para que
    # dos pasajeros seguidos nunca compitan por el mismo tramo de pasillo:
    # esa es la idea central del metodo, no solo el orden ventanilla-centro-pasillo.
    por_fila_y_tipo = {}
    for p in pasajeros:
        por_fila_y_tipo.setdefault((p.fila, p.tipo_asiento), []).append(p)

    filas_impares = _espaciar([f for f in range(1, ROWS + 1) if f % 2 == 1])
    filas_pares = _espaciar([f for f in range(1, ROWS + 1) if f % 2 == 0])

    orden = []
    for tipo in SEAT_TYPES:
        for grupo_filas in (filas_impares, filas_pares):
            for fila in grupo_filas:
                orden.extend(por_fila_y_tipo[(fila, tipo)])
    return orden


METODOS = {
    "Trasera a delantera": orden_trasera_delantera,
    "Aleatorio": orden_aleatorio,
    "WilMA (ventanilla-centro-pasillo)": orden_wilma,
    "Steffen": orden_steffen,
}


# --- Simulacion del pasillo (automata celular, resolucion 1 segundo) ---

def simular_embarque(orden_pasajeros, intervalo_entrada=1.2, interferencia_asiento=12):
    """
    Cada celda del pasillo = una fila. Un pasajero avanza una celda por
    segundo si la siguiente esta libre. Al llegar a su fila se detiene
    tiempo_guardado segundos (+ interferencia si tiene que esperar a que
    se levante alguien ya sentado en su fila del mismo lado).
    """
    n = len(orden_pasajeros)
    posicion = [-1] * n          # -1 = todavia no ha entrado al pasillo
    en_pasillo = [False] * n
    guardando_hasta = [None] * n
    sentado = [False] * n
    ocupado_fila_lado = {}       # (fila, lado) -> set de rangos de asiento ya sentados

    siguiente_entrada = 0
    t = 0
    fin = [None] * n

    while not all(sentado):
        # dejar entrar al siguiente pasajero si toca y la celda 0 esta libre
        if siguiente_entrada < n and t >= siguiente_entrada * intervalo_entrada:
            libre = all(not en_pasillo[j] or posicion[j] != 0 for j in range(n))
            if libre:
                posicion[siguiente_entrada] = 0
                en_pasillo[siguiente_entrada] = True
                siguiente_entrada += 1

        ocupantes_por_celda = {}
        for i in range(n):
            if en_pasillo[i] and not sentado[i]:
                ocupantes_por_celda[posicion[i]] = i

        for i in range(n):
            if not en_pasillo[i] or sentado[i]:
                continue
            p = orden_pasajeros[i]
            if guardando_hasta[i] is not None:
                if t >= guardando_hasta[i]:
                    sentado[i] = True
                    en_pasillo[i] = False
                    fin[i] = t
                    ocupados = ocupado_fila_lado.setdefault((p.fila, p.lado), set())
                    ocupados.add(SEAT_RANK[p.tipo_asiento])
                continue
            if posicion[i] == p.fila:
                extra = 0
                # si ya hay alguien sentado mas cerca del pasillo que este
                # asiento, tiene que levantarse para dejarle pasar
                nivel_necesario = SEAT_RANK[p.tipo_asiento]
                ocupados = ocupado_fila_lado.get((p.fila, p.lado), set())
                if any(rango > nivel_necesario for rango in ocupados):
                    extra = interferencia_asiento
                guardando_hasta[i] = t + p.tiempo_guardado + extra
                continue
            siguiente_pos = posicion[i] + 1
            ocupante = ocupantes_por_celda.get(siguiente_pos)
            if ocupante is None or ocupante == i:
                posicion[i] = siguiente_pos

        t += 1
        if t > 10000:
            raise RuntimeError("la simulacion no converge, revisar el modelo")

    return max(fin)


def aplicar_incumplimiento(orden, rng, prob_salto=0.0):
    """
    El mundo real que no sale en el paper: familias que no quieren separarse,
    gente que llega tarde y se cuela, pasajeros que ignoran su grupo de
    embarque. Cada pasajero, con probabilidad prob_salto, deja de respetar
    la posicion que le tocaba y se cuela en un hueco aleatorio de la cola.
    """
    n = len(orden)
    indices_incumplen = [i for i in range(n) if rng.random() < prob_salto]
    if not indices_incumplen:
        return orden
    incumplidores = [orden[i] for i in indices_incumplen]
    rng.shuffle(incumplidores)
    resultado = orden[:]
    for indice, pasajero in zip(indices_incumplen, incumplidores):
        resultado[indice] = pasajero
    return resultado


def comparar_metodos(rows=ROWS, trials=200, seed_base=1000, prob_salto=0.0):
    resultados = {nombre: [] for nombre in METODOS}
    for trial in range(trials):
        rng = random.Random(seed_base + trial)
        pasajeros = generar_pasajeros(rows, seed=seed_base + trial)
        for nombre, fn in METODOS.items():
            orden = fn(pasajeros, rng)
            orden = aplicar_incumplimiento(orden, rng, prob_salto)
            tiempo = simular_embarque(orden)
            resultados[nombre].append(tiempo)
    return resultados


def imprimir_tabla(resultados):
    print(f"{'Metodo':35s} {'Media (s)':>10s} {'Desv (s)':>10s}")
    for nombre, tiempos in sorted(resultados.items(), key=lambda kv: mean(kv[1])):
        print(f"{nombre:35s} {mean(tiempos):10.1f} {stdev(tiempos):10.1f}")


if __name__ == "__main__":
    for prob in (0.0, 0.15, 0.35):
        print(f"n--- % de pasajeros que no respetan su turno: {prob:.0%} ---")
        resultados = comparar_metodos(rows=20, trials=200, prob_salto=prob)
        imprimir_tabla(resultados)

Con 20 filas, 6 asientos por fila y 200 repeticiones por método, esto es lo que sale en mi máquina:

--- % de pasajeros que no respetan su turno: 0% ---
Metodo                               Media (s)   Desv (s)
WilMA (ventanilla-centro-pasillo)        940.0       48.1
Aleatorio                               1147.3       64.2
Steffen                                 1155.9       45.0
Trasera a delantera                     1534.8       74.8

--- % de pasajeros que no respetan su turno: 15% ---
Metodo                               Media (s)   Desv (s)
WilMA (ventanilla-centro-pasillo)        983.3       54.4
Steffen                                 1144.1       54.7
Aleatorio                               1152.4       72.6
Trasera a delantera                     1444.4       79.6

--- % de pasajeros que no respetan su turno: 35% ---
Metodo                               Media (s)   Desv (s)
WilMA (ventanilla-centro-pasillo)       1035.7       58.4
Steffen                                 1137.9       57.7
Aleatorio                               1150.9       63.8
Trasera a delantera                     1310.7       77.9

Hay tres cosas ahí que llaman la atención.

Incluso en un modelo de juguete e idealizado, ordenar por tipo de asiento (WilMA, Steffen) le saca una ventaja clara al método por zonas, exactamente en la misma dirección que marca el paper real.

Lo curioso es que en mi simulación WilMA queda por delante de Steffen incluso sin ningún incumplimiento, algo que las cifras del paper original no muestran, pero que coincide con lo que encontró United Airlines cuando probó los dos métodos con pasajeros de carne y hueso en vez de con voluntarios pagados. Los detalles finos de un modelo (cómo se reparte exactamente el orden dentro de cada bloque, cuánto cuesta cada interferencia) cambian quién gana, y eso ya debería hacernos desconfiar un poco de los titulares que afirman que «el problema está resuelto».

El dato que de verdad importa para el argumento de este artículo es otro: cuando metemos ruido (un 15%, un 35% de pasajeros que no respetan su posición asignada, que es justo lo que pasa con niños, familias y gente con prisa), el método trasera a delantera mejora, porque el ruido rompe la aglomeración de gente compitiendo por el mismo tramo corto de pasillo, que era justo lo que lo hacía tan lento. Steffen y WilMA, en cambio, apenas se mueven o incluso empeoran un poco, porque su ventaja depende por completo de que la cola respete un orden fino que en la vida real nadie va a hacer cumplir.

La lección para quien programa

Esto lo veo cada semana con modelos de machine learning. El modelo perfecto en el notebook, con el dataset limpio y las métricas bonitas, no vale nada si la persona que tiene que usarlo en producción no puede aplicarlo, no lo entiende o directamente no confía en él. Cuanto más fina es la solución teórica, más frágil resulta frente a la realidad: esconde más excepciones y exige más disciplina de gente que no ha firmado para seguir un protocolo de laboratorio.

En un proyecto de operaciones en Iberia entendí esto de otra forma. Cada «solo hay que hacer X» que suena simple en una reunión son, en realidad, tres restricciones operativas que nadie mencionó: el turno de la tripulación, el convenio del personal de tierra, la ventana de conexión con el siguiente vuelo. Cuanto más simple parece una solución desde fuera, más probable es que alguien ya haya intentado aplicarla y se haya encontrado con esas restricciones.

El método Steffen no está mal, ni mucho menos: es un ejercicio de simulación bien hecho, publicado en una revista revisada por pares, con datos experimentales reales que lo respaldan. El problema es venderlo como una implementación resuelta cuando solo es una simulación con los bordes limados. Innovar sigue siendo obligatorio, y siempre hay margen para mejorar cómo hacemos las cosas. Pero la solución no está en encontrar el algoritmo perfecto sobre el papel: está en mover la aguja dentro de las restricciones reales, que casi nunca caben en un vídeo de un minuto.

Fuentes: Steffen, J. H., «Optimal boarding method for airline passengers», Journal of Air Transport Management, 2008; «Experimental test of airplane boarding methods», arXiv:1108.5211; Jalopnik, «There's A Faster Way To Board A Plane, But Airlines Won't Use It».

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP